open Ast

module NameMap = Map.Make(struct
  type t = string
  let compare x y = Pervasives.compare x y
end)

exception ReturnException of int * int NameMap.t

(* Generates string version of variable declarations. Returns string *)
let string_of_vdecl vdecl = vdecl.vartype ^ " " ^ vdecl.varname ^ ";\n"

(* initialize global variable symbol table *)
let globals = NameMap.empty 

(* Main entry point: interpret a program *)

let interpret (vars, funcs) =
  (* Put function declarations in a symbol table *)
  let func_decls = List.fold_left
      (fun funcs fdecl -> NameMap.add fdecl.fname fdecl funcs)
      NameMap.empty funcs  
	in
	(* Put global variable declarations in a symbol table *)
	(* Checks to make sure the same name doesn't get used more than once*)
	 
	let globals = List.fold_left 
		 (fun gvars vdecl -> 
				if (NameMap.mem vdecl.varname globals)
					then raise (Failure (" Duplicate variable: " ^ vdecl.varname ))
				else NameMap.add vdecl.varname vdecl gvars) globals vars ;
  in
				
  (* Returns the string representation of the function *)
  let rec call fdecl actuals globals =
		
    (* Evaluate an expression and return (intermediate code) *)
    let rec string_of_expr env = function
	 IntLiteral(i) -> string_of_int i ^ "\n" 
      | Noexpr -> " 1 \n"  (* must be non-zero for the for loop predicate *)
      | Id(var) ->
	  let locals, globals = env in
	  if NameMap.mem var locals then
	   string_of_int (NameMap.find var locals)^"\n"
	  else if NameMap.mem var globals then
	     string_of_int (NameMap.find var globals) ^ "\n"
	  else raise (Failure ("undeclared identifier " ^ var))
      | Not (v) -> 
				let v1 = string_of_expr env v in "!" ^ v1 ^ "\n"
			| BoolLiteral (v) -> string_of_bool v ^ "\n"
			| Binop(e1, op, e2) ->
	  let v1 = string_of_expr env e1 in
          let v2 = string_of_expr env e2 in
	  (match op with
	    Add ->  v1 ^ "+" ^ v2 ^ "\n"
	  | Sub ->   v1 ^ "-" ^  v2  ^ "\n"
	  | Mult ->   v1 ^ "*" ^ v2  ^ "\n"
	  | Div ->   v1 ^ "/" ^ v2 ^ "\n"
	  | Equal -> v1 ^ "==" ^ v2  ^ "\n"
	  | Neq -> v1 ^ "!=" ^ v2  ^ "\n"
	  | Less -> v1 ^ "<" ^ v2  ^ "\n"
	  | Leq -> v1 ^ "<=" ^ v2 ^ "\n"
	  | Greater -> v1 ^ ">" ^ v2 ^ "\n"
	  | Geq -> v1 ^ ">=" ^ v2 ^ "\n"
		| Mod -> v1 ^ "%" ^ v2  ^ "\n"
		| Or -> v1 ^ "||" ^ v2  ^ "\n"
		| And -> v1 ^ "%" ^ v2  ^ "\n")
    	| Assign(var, e) -> " \n"
	  (*let v, (locals, globals) = string_of_expr env e in
	  if NameMap.mem var locals then
	    string_of_int v, (NameMap.add var v locals, globals)
	  else if NameMap.mem var globals then
	    string_of_int v, (locals, NameMap.add var v globals)
	  else raise (Failure ("undeclared identifier " ^ var))*)
    | Call("print", [e]) -> " " 
	  (*let v, env = string_of_expr env e in
	  print_endline (string_of_int v);
	  0, env *)
      | Call(f, actuals) -> " \n"
	  (*let fdecl =
	    try NameMap.find f func_decls
	    with Not_found -> raise (Failure ("undefined function " ^ f)) 
	  in
	  let actuals, env = List.fold_left
	      (fun (actuals, env) actual ->
		let v, env = string_of_expr env actual in v :: actuals, env)
   	      ([], env) (List.rev actuals)
	  in
	  let (locals, globals) = env in
	  try
	    let globals = call fdecl actuals globals
	    in 0, (locals, globals)
	  with ReturnException(v, globals) -> v, (locals, globals)*)
    in

    (* return a string version for a statement *)
    let rec string_of_stmt env = function
				Block(stmts) ->  "{\n" ^ List.fold_left string_of_stmt env stmts ^ "}\n" 
      | Expr(e) ->  string_of_expr env e
      | If(e, s1, s2) -> 
				"if (" ^ string_of_expr env e ^ "){" ^  
				string_of_stmt env s1 ^ "}\n else {" ^ 
				string_of_stmt env s2 ^"}\n"
      | While(e, s) -> "while (" ^ string_of_expr env e ^ "){ " ^ string_of_stmt env s ^ "}\n"
      | For(e1, e2, e3, s) -> 
				" for(" ^ string_of_expr env e1 ^ "; " ^ 
				string_of_expr env e2 ^ "; " ^ 
				string_of_expr env e3 ^ "){ " ^
				string_of_stmt env s ^ "} \n" 
		  | Return(e) -> "return " ^ string_of_expr env e
	
 (*  in *)

    (* Enter the function: bind actual values to formal arguments *)
  (*  let locals =
      try List.fold_left2
	  (fun locals formal actual -> NameMap3.add formal actual locals)
	  NameMap3.empty fdecl.formals actuals
      with Invalid_argument(_) ->
	raise (Failure ("wrong number of arguments passed to " ^ fdecl.fname)) *)
    in 
			
    (* symbol table for arg names and local variables) *)
		(* Checks to make sure the same name doesn't get used more than once*)
		(* formals *)
		let locals = NameMap.empty
		in

			let locals = List.fold_left 
		 (fun lvars fargs -> 
				if (NameMap.mem fargs.varname locals)
					then raise (Failure (" Duplicate variable: " ^ fargs.varname ))
				else NameMap.add fargs.varname fargs lvars) locals fdecl.formals 
		(* local vars *)
		in
		let locals = List.fold_left 
		 (fun lvars fvars -> 
				if (NameMap.mem fvars.varname locals)
					then raise (Failure (" Duplicate variable: " ^ fvars.varname ))
				else NameMap.add fvars.varname fvars lvars) locals fdecl.locals
		in
	
		(* string of intermediate code  for a given function*)
		List.fold_left string_of_stmt (locals, globals) fdecl.body 
 
  in  try
		(* Intermediate code of global variables and main*)
		(* TODO loop through list of function decl and generate string rep *)
 		List.fold_left string_of_vdecl "" vars ^  call (NameMap.find "main" func_decls) [] globals
  	with Not_found -> raise (Failure ("did not find the main() function"))